// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/strings/string16.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/browser/frame_host/navigation_entry_impl.h"
#include "content/browser/site_instance_impl.h"
#include "content/public/browser/ssl_status.h"
#include "testing/gtest/include/gtest/gtest.h"

using base::ASCIIToUTF16;

namespace content {

class NavigationEntryTest : public testing::Test {
public:
    NavigationEntryTest()
        : instance_(NULL)
    {
    }

    void SetUp() override
    {
        entry1_.reset(new NavigationEntryImpl);

        instance_ = SiteInstanceImpl::Create(NULL);
        entry2_.reset(new NavigationEntryImpl(
            instance_,
            GURL("test:url"),
            Referrer(GURL("from"), blink::WebReferrerPolicyDefault),
            ASCIIToUTF16("title"),
            ui::PAGE_TRANSITION_TYPED,
            false));
    }

    void TearDown() override { }

protected:
    std::unique_ptr<NavigationEntryImpl> entry1_;
    std::unique_ptr<NavigationEntryImpl> entry2_;
    // SiteInstances are deleted when their NavigationEntries are gone.
    scoped_refptr<SiteInstanceImpl> instance_;
};

// Test unique ID accessors
TEST_F(NavigationEntryTest, NavigationEntryUniqueIDs)
{
    // Two entries should have different IDs by default
    EXPECT_NE(entry1_->GetUniqueID(), entry2_->GetUniqueID());

    // Can set an entry to have the same ID as another
    entry2_->set_unique_id(entry1_->GetUniqueID());
    EXPECT_EQ(entry1_->GetUniqueID(), entry2_->GetUniqueID());
}

// Test URL accessors
TEST_F(NavigationEntryTest, NavigationEntryURLs)
{
    // Start with no virtual_url (even if a url is set)
    EXPECT_FALSE(entry1_->has_virtual_url());
    EXPECT_FALSE(entry2_->has_virtual_url());

    EXPECT_EQ(GURL(), entry1_->GetURL());
    EXPECT_EQ(GURL(), entry1_->GetVirtualURL());
    EXPECT_TRUE(entry1_->GetTitleForDisplay().empty());

    // Setting URL affects virtual_url and GetTitleForDisplay
    entry1_->SetURL(GURL("http://www.google.com"));
    EXPECT_EQ(GURL("http://www.google.com"), entry1_->GetURL());
    EXPECT_EQ(GURL("http://www.google.com"), entry1_->GetVirtualURL());
    EXPECT_EQ(ASCIIToUTF16("www.google.com"), entry1_->GetTitleForDisplay());

    // Setting URL with RTL characters causes it to be wrapped in an LTR
    // embedding.
    entry1_->SetURL(GURL("http://www.xn--rgba6eo.com"));
    EXPECT_EQ(base::WideToUTF16(L"\x202a"
                                L"www.\x062c\x0648\x062c\x0644"
                                L".com\x202c"),
        entry1_->GetTitleForDisplay());

    // file:/// URLs should only show the filename.
    entry1_->SetURL(GURL("file:///foo/bar baz.txt"));
    EXPECT_EQ(ASCIIToUTF16("bar baz.txt"), entry1_->GetTitleForDisplay());

    // file:/// URLs should *not* be wrapped in an LTR embedding.
    entry1_->SetURL(GURL("file:///foo/%D8%A7%D8%A8 %D8%AC%D8%AF.txt"));
    EXPECT_EQ(base::WideToUTF16(L"\x0627\x0628"
                                L" \x062c\x062f"
                                L".txt"),
        entry1_->GetTitleForDisplay());

    // For file:/// URLs, make sure that slashes after the filename are ignored.
    // Regression test for https://crbug.com/503003.
    entry1_->SetURL(GURL("file:///foo/bar baz.txt#foo/bar"));
    EXPECT_EQ(ASCIIToUTF16("bar baz.txt#foo/bar"), entry1_->GetTitleForDisplay());
    entry1_->SetURL(GURL("file:///foo/bar baz.txt?x=foo/bar"));
    EXPECT_EQ(ASCIIToUTF16("bar baz.txt?x=foo/bar"),
        entry1_->GetTitleForDisplay());
    entry1_->SetURL(GURL("file:///foo/bar baz.txt#baz/boo?x=foo/bar"));
    EXPECT_EQ(ASCIIToUTF16("bar baz.txt#baz/boo?x=foo/bar"),
        entry1_->GetTitleForDisplay());
    entry1_->SetURL(GURL("file:///foo/bar baz.txt?x=foo/bar#baz/boo"));
    EXPECT_EQ(ASCIIToUTF16("bar baz.txt?x=foo/bar#baz/boo"),
        entry1_->GetTitleForDisplay());
    entry1_->SetURL(GURL("file:///foo/bar baz.txt#foo/bar#baz/boo"));
    EXPECT_EQ(ASCIIToUTF16("bar baz.txt#foo/bar#baz/boo"),
        entry1_->GetTitleForDisplay());
    entry1_->SetURL(GURL("file:///foo/bar baz.txt?x=foo/bar?y=baz/boo"));
    EXPECT_EQ(ASCIIToUTF16("bar baz.txt?x=foo/bar?y=baz/boo"),
        entry1_->GetTitleForDisplay());

    // Title affects GetTitleForDisplay
    entry1_->SetTitle(ASCIIToUTF16("Google"));
    EXPECT_EQ(ASCIIToUTF16("Google"), entry1_->GetTitleForDisplay());

    // Setting virtual_url doesn't affect URL
    entry2_->SetVirtualURL(GURL("display:url"));
    EXPECT_TRUE(entry2_->has_virtual_url());
    EXPECT_EQ(GURL("test:url"), entry2_->GetURL());
    EXPECT_EQ(GURL("display:url"), entry2_->GetVirtualURL());

    // Having a title set in constructor overrides virtual URL
    EXPECT_EQ(ASCIIToUTF16("title"), entry2_->GetTitleForDisplay());

    // User typed URL is independent of the others
    EXPECT_EQ(GURL(), entry1_->GetUserTypedURL());
    EXPECT_EQ(GURL(), entry2_->GetUserTypedURL());
    entry2_->set_user_typed_url(GURL("typedurl"));
    EXPECT_EQ(GURL("typedurl"), entry2_->GetUserTypedURL());
}

// Test Favicon inner class construction.
TEST_F(NavigationEntryTest, NavigationEntryFavicons)
{
    EXPECT_EQ(GURL(), entry1_->GetFavicon().url);
    EXPECT_FALSE(entry1_->GetFavicon().valid);
}

// Test SSLStatus inner class
TEST_F(NavigationEntryTest, NavigationEntrySSLStatus)
{
    // Default (unknown)
    EXPECT_FALSE(entry1_->GetSSL().initialized);
    EXPECT_FALSE(entry2_->GetSSL().initialized);
    EXPECT_FALSE(!!entry1_->GetSSL().certificate);
    EXPECT_EQ(0U, entry1_->GetSSL().cert_status);
    EXPECT_EQ(-1, entry1_->GetSSL().security_bits);
    int content_status = entry1_->GetSSL().content_status;
    EXPECT_FALSE(!!(content_status & SSLStatus::DISPLAYED_INSECURE_CONTENT));
    EXPECT_FALSE(!!(content_status & SSLStatus::RAN_INSECURE_CONTENT));
}

// Test other basic accessors
TEST_F(NavigationEntryTest, NavigationEntryAccessors)
{
    // SiteInstance
    EXPECT_TRUE(entry1_->site_instance() == NULL);
    EXPECT_EQ(instance_, entry2_->site_instance());
    entry1_->set_site_instance(instance_);
    EXPECT_EQ(instance_, entry1_->site_instance());

    // Page type
    EXPECT_EQ(PAGE_TYPE_NORMAL, entry1_->GetPageType());
    EXPECT_EQ(PAGE_TYPE_NORMAL, entry2_->GetPageType());
    entry2_->set_page_type(PAGE_TYPE_INTERSTITIAL);
    EXPECT_EQ(PAGE_TYPE_INTERSTITIAL, entry2_->GetPageType());

    // Referrer
    EXPECT_EQ(GURL(), entry1_->GetReferrer().url);
    EXPECT_EQ(GURL("from"), entry2_->GetReferrer().url);
    entry2_->SetReferrer(
        Referrer(GURL("from2"), blink::WebReferrerPolicyDefault));
    EXPECT_EQ(GURL("from2"), entry2_->GetReferrer().url);

    // Title
    EXPECT_EQ(base::string16(), entry1_->GetTitle());
    EXPECT_EQ(ASCIIToUTF16("title"), entry2_->GetTitle());
    entry2_->SetTitle(ASCIIToUTF16("title2"));
    EXPECT_EQ(ASCIIToUTF16("title2"), entry2_->GetTitle());

    // State
    EXPECT_FALSE(entry1_->GetPageState().IsValid());
    EXPECT_FALSE(entry2_->GetPageState().IsValid());
    entry2_->SetPageState(PageState::CreateFromEncodedData("state"));
    EXPECT_EQ("state", entry2_->GetPageState().ToEncodedData());

    // Transition type
    EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
        entry1_->GetTransitionType(), ui::PAGE_TRANSITION_LINK));
    EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
        entry2_->GetTransitionType(), ui::PAGE_TRANSITION_TYPED));
    entry2_->SetTransitionType(ui::PAGE_TRANSITION_RELOAD);
    EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
        entry2_->GetTransitionType(), ui::PAGE_TRANSITION_RELOAD));

    // Is renderer initiated
    EXPECT_FALSE(entry1_->is_renderer_initiated());
    EXPECT_FALSE(entry2_->is_renderer_initiated());
    entry2_->set_is_renderer_initiated(true);
    EXPECT_TRUE(entry2_->is_renderer_initiated());

    // Post Data
    EXPECT_FALSE(entry1_->GetHasPostData());
    EXPECT_FALSE(entry2_->GetHasPostData());
    entry2_->SetHasPostData(true);
    EXPECT_TRUE(entry2_->GetHasPostData());

    // Restored
    EXPECT_EQ(RestoreType::NONE, entry1_->restore_type());
    EXPECT_FALSE(entry1_->IsRestored());
    EXPECT_EQ(RestoreType::NONE, entry2_->restore_type());
    EXPECT_FALSE(entry2_->IsRestored());
    entry2_->set_restore_type(RestoreType::LAST_SESSION_EXITED_CLEANLY);
    EXPECT_EQ(RestoreType::LAST_SESSION_EXITED_CLEANLY, entry2_->restore_type());
    EXPECT_TRUE(entry2_->IsRestored());

    // Original URL
    EXPECT_EQ(GURL(), entry1_->GetOriginalRequestURL());
    EXPECT_EQ(GURL(), entry2_->GetOriginalRequestURL());
    entry2_->SetOriginalRequestURL(GURL("original_url"));
    EXPECT_EQ(GURL("original_url"), entry2_->GetOriginalRequestURL());

    // User agent override
    EXPECT_FALSE(entry1_->GetIsOverridingUserAgent());
    EXPECT_FALSE(entry2_->GetIsOverridingUserAgent());
    entry2_->SetIsOverridingUserAgent(true);
    EXPECT_TRUE(entry2_->GetIsOverridingUserAgent());

    // Post data
    EXPECT_FALSE(entry1_->GetPostData());
    EXPECT_FALSE(entry2_->GetPostData());
    const int length = 11;
    const char* raw_data = "post\n\n\0data";
    scoped_refptr<ResourceRequestBody> post_data = ResourceRequestBody::CreateFromBytes(raw_data, length);
    entry2_->SetPostData(post_data);
    EXPECT_EQ(post_data, entry2_->GetPostData());
}

// Test basic Clone behavior.
TEST_F(NavigationEntryTest, NavigationEntryClone)
{
    // Set some additional values.
    entry2_->SetTransitionType(ui::PAGE_TRANSITION_RELOAD);
    entry2_->set_should_replace_entry(true);

    std::unique_ptr<NavigationEntryImpl> clone(entry2_->Clone());

    // Value from FrameNavigationEntry.
    EXPECT_EQ(entry2_->site_instance(), clone->site_instance());

    // Value from constructor.
    EXPECT_EQ(entry2_->GetTitle(), clone->GetTitle());

    // Value set after constructor.
    EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
        clone->GetTransitionType(), entry2_->GetTransitionType()));

    // Value not copied due to ResetForCommit.
    EXPECT_NE(entry2_->should_replace_entry(), clone->should_replace_entry());
}

// Test timestamps.
TEST_F(NavigationEntryTest, NavigationEntryTimestamps)
{
    EXPECT_EQ(base::Time(), entry1_->GetTimestamp());
    const base::Time now = base::Time::Now();
    entry1_->SetTimestamp(now);
    EXPECT_EQ(now, entry1_->GetTimestamp());
}

// Test extra data stored in the navigation entry.
TEST_F(NavigationEntryTest, NavigationEntryExtraData)
{
    base::string16 test_data = ASCIIToUTF16("my search terms");
    base::string16 output;
    entry1_->SetExtraData("search_terms", test_data);

    EXPECT_FALSE(entry1_->GetExtraData("non_existent_key", &output));
    EXPECT_EQ(ASCIIToUTF16(""), output);
    EXPECT_TRUE(entry1_->GetExtraData("search_terms", &output));
    EXPECT_EQ(test_data, output);
    // Data is cleared.
    entry1_->ClearExtraData("search_terms");
    // Content in |output| is not modified if data is not present at the key.
    EXPECT_FALSE(entry1_->GetExtraData("search_terms", &output));
    EXPECT_EQ(test_data, output);
    // Using an empty string shows that the data is not present in the map.
    base::string16 output2;
    EXPECT_FALSE(entry1_->GetExtraData("search_terms", &output2));
    EXPECT_EQ(ASCIIToUTF16(""), output2);
}

} // namespace content
